import { addComponent, addEntity, getEntityComponents, hasComponent, removeEntity } from '$lib/ecs';
import { Creature, Description, Feature, Glyph, Interesting } from './components';
import { decode } from './util';
import { Item } from './item';
import { getPos2 } from './pos2';
import { processEyes } from './eyes';
import { Ground } from './content/ground';
import { getMemory } from './memory';
import { Elevation, Flooded } from './content/flooding';
import { Wall } from '../routes/djii/wall';
import { Door } from './door';
import { getPlayer } from './player';

const rememberEntities = (world, entities) => {
  return entities.map((e) => {
    const clone = addEntity(world);
    addComponent(world, Glyph, clone);
    Glyph.glyph[clone] = Glyph.glyph[e];
    Glyph.color[clone] = Glyph.color[e];
    Glyph.background[clone] = Glyph.background[e];
    addComponent(world, Description, clone);
    Description.summary[clone] = Description.summary[e];
    Description.description[clone] = Description.description[e];
    // TODO: make remembered components customizeable
    for (let component of [Feature, Item, Creature, Ground, Wall, Door]) {
      if (hasComponent(world, component, e)) {
        addComponent(world, component, clone);
      }
    }
    return clone;
  });
};

// NOTE: collecting interesting objects is done here purely for performance reasons -
// otherwise more logically to have it elsewhere
export const updateMemoryAndInterestingObjects = (world, x, y, mem, vision) => {
  let features = [];
  let creatures = [];
  let items = [];
  let all = [];
  let currentTile;
  for (let [dy, vline] of vision) {
    let line = mem.get(y+dy);
    if (line === undefined) {
      line = new Map();
      mem.set(y+dy, line);
    }
    for (let [dx, tile] of vline) {
      const oldtile = line.get(x+dx);
      if (tile.visible) {
        if (oldtile !== undefined) {
          for (let e of oldtile.stack) {
            removeEntity(world, e);
          }
        }
        line.set(x+dx, {
          stack: rememberEntities(world, tile.stack),
          visible: false,
        });
        // Collect interesting objects
        if (dx === 0 && dy === 0) {
          currentTile = [...tile.stack];
        } else {
          for (let e of [...tile.stack]) {
            if (hasComponent(world, Interesting, e) && hasComponent(world, Feature, e)) {
              features.push(e);
            } else if (hasComponent(world, Creature, e)) {
              creatures.push(e);
            } else if (hasComponent(world, Item, e)) {
              items.push(e);
            }
          }
        }
        all = [...all, ...tile.stack];
      }
    }
  }
  return {features, creatures, items, all, currentTile};
};

export const renderTiles = (renderStack, world, r) => {
  const pc = getPlayer(world);
  const {x, y} = getPos2(pc);
  const vision = processEyes(world, pc);
  const mem = getMemory(pc);
  const interesting = updateMemoryAndInterestingObjects(world, x, y, mem, vision);
  return [renderVisionAndMemory(renderStack, world, vision, r, mem, x, y), interesting];
};

export const renderVisionAndMemory = (renderStack, world, vision, r, mem, x, y) => {
  const width = r*2+1;
  const height = r*2+1;
  let res = Array.from({ length: height }, () => Array.from({ length: width }, () => {
    return {
      glyph: ' ',
      visible: false,
      stack: [],
    };
  }));
  for (let sy = 0; sy < height; ++sy) {
    const dy = sy-r;
    const line = vision.get(dy);
    for (let sx = 0; sx < width; ++sx) {
      const dx = sx-r;
      if (line !== undefined && line.get(dx) !== undefined && line.get(dx).visible) {
        const {stack} = line.get(dx);
        const rendered = renderStack(world, stack);
        res[sy][sx] = {
          ...rendered,
          visible: true,
          // NOTE that [...stack] here is only required because of hard to trace bug in svelte reactivity
          stack: [...stack],
          x: x+dx,
          y: y+dy,
        };
      } else if (mem && mem.has(y+dy)) {
        const tile = mem.get(y+dy).get(x+dx);
        if (tile !== undefined) {
          const rendered = renderStack(world, tile.stack);
          res[sy][sx] = {
            ...rendered,
            visible: false,
            stack: tile.stack,
            x: x+dx,
            y: y+dy,
          };
        }
      }
    }
  }
  return res;
};

export const renderStack = (world, stack) => {
  let entity, etype;
  let elevation = 0;
  let flooded = 0;
  for (let e of stack) {
    if (hasComponent(world, Creature, e)) {
      entity = e;
      etype = 'creature';
    } else if (hasComponent(world, Item, e) && etype !== 'creature') {
      entity = e;
      etype = 'item';
    } else if (hasComponent(world, Feature, e) && etype !== 'item' && etype !== 'creature') {
      entity = e;
      etype = 'feature';
    } else if (hasComponent(world, Flooded, e) && etype !== 'feature' && etype !== 'item' && etype !== 'creature') {
      entity = e;
      etype = 'flooded';
    } else if (hasComponent(world, Ground, e)) {
      if (etype === undefined) {
        entity = e;
        etype = 'ground';
      }
    }
    if (hasComponent(world, Elevation, e)) {
      elevation = Elevation.level[e];
    }
    if (hasComponent(world, Flooded, e)) {
      flooded = Flooded.level[e];
    }
  }
  if (entity === undefined) {
    return { glyph: ' ' };
  }
  const glyph = decode(Glyph.glyph[entity]);
  const color = decode(Glyph.color[entity]);
  // TODO: make modular
  if (etype === 'flooded') {
    const level = Flooded.level[entity];
    if (level > 0) {
      const amount = Math.min(level/20, 1);
      return {
        glyph: level > 10 ? '~' : glyph,
        color: `color-mix(in hsl longer hue, #05d ${amount*100}%, ${color})`,
      };
    }
  }
  return {
    glyph,
    color,
  };
};
